package com.ggshily.game.model.ms3d
{
	import __AS3__.vec.Vector;
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import org.papervision3d.core.math.Matrix3D;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.core.math.Quaternion;
	
	public class MS3DFile
	{
		//
		// max values
		//
		public static const MAX_VERTICES : int =   65534;
		public static const MAX_TRIANGLES : int =  65534;
		public static const MAX_GROUPS : int =     255;
		public static const MAX_MATERIALS : int =  128;
		public static const MAX_JOINTS : int =     128;
		
		//
		// flags
		//
		public static const SELECTED : int =       1;
		public static const HIDDEN : int =         2;
		public static const SELECTED2 : int =      4;
		public static const DIRTY : int =          8;
		
		public static const MS3D_ID : String = "MS3D000000";
		public static const MS3D_VERSION : int = 4;

		public var arrVertices : Vector.<Ms3d_vertex>;
		public var arrTriangles : Vector.<Ms3d_triangle>;
		public var arrEdges : Vector.<Ms3d_edge>;
		public var arrGroups : Vector.<Ms3d_group>;
		public var arrMaterials : Vector.<Ms3d_material>;
		public var animationFPS : Number;
		public var currentTime : Number;
		public var totalFrames : int;
		public var arrJoints : Vector.<Ms3d_joint>;
		
		public var jointSize : Number;
		public var transparencyMode : int;
		public var alphaRef : Number;
		
		private var arrBones : Array;
		
		public function MS3DFile(bytes : ByteArray = null)
		{
			if(bytes)
			{
				load(bytes);
			}
		}

		public function load(bytes : ByteArray) : void
		{
			bytes.position = 0;
			bytes.endian = Endian.LITTLE_ENDIAN;
			
			var header : Ms3d_header = new Ms3d_header(bytes);
			if(header.id != MS3D_ID || header.version != MS3D_VERSION)
			{
				throw new Error("wrong file type!!!");
			}
			
			arrBones = []
			// vertices
			var numVertices : int = bytes.readShort();
			arrVertices = new Vector.<Ms3d_vertex>(numVertices);
			var i : int;
			for(i = 0; i < numVertices; i++)
			{
				arrVertices[i] = new Ms3d_vertex(bytes);
				if(arrBones.indexOf(arrVertices[i].boneId) < 0)
				{
					arrBones.push(arrVertices[i].boneId);
				}
			}
			
			// triangles
			var numTriangles : int = bytes.readShort();
			arrTriangles = new Vector.<Ms3d_triangle>(numTriangles);
			for(i = 0; i < numTriangles; i++)
			{
				var triangle : Ms3d_triangle = new Ms3d_triangle(bytes);
				arrTriangles[i] = triangle;
			}
			
			// edges
			
			
			// groups
			var numGroups : int = bytes.readShort();
			arrGroups = new Vector.<Ms3d_group>(numGroups);
			for(i = 0; i < numGroups; i++)
			{
				arrGroups[i] = new Ms3d_group(bytes);
			}
			
			// materials
			var numMaterials : int = bytes.readShort();
			arrMaterials = new Vector.<Ms3d_material>(numMaterials);
			for(i = 0; i < numMaterials; i++)
			{
				arrMaterials[i] = new Ms3d_material(bytes);
			}
			
			animationFPS = bytes.readFloat();
			currentTime = bytes.readFloat();
			totalFrames = bytes.readInt();
			
			// joints
			var numJoints : int = bytes.readShort();
			arrJoints = new Vector.<Ms3d_joint>(numJoints);
			for(i = 0; i < numJoints; i++)
			{
				arrJoints[i] = new Ms3d_joint(bytes, animationFPS);
			}
			
			var subVersion : int;
			
			// comments
			if(bytes.position < bytes.length)
			{
				subVersion = bytes.readInt();
				if(subVersion == 1)
				{
					// group comments
					var numComments : int = bytes.readInt();
					for(i = 0; i < numComments; i++)
					{
						new Ms3d_comment(bytes);
					}

					// material comments
					numComments = bytes.readInt();
					for(i = 0; i < numComments; i++)
					{
						new Ms3d_comment(bytes);
					}

					// joint comments
					numComments = bytes.readInt();
					for(i = 0; i < numComments; i++)
					{
						new Ms3d_comment(bytes);
					}

					// model comments
					numComments = bytes.readInt();
					for(i = 0; i < numComments; i++)
					{
						new Ms3d_comment(bytes);
					}
				}
			}
			
			// vertex extra
			if(bytes.position < bytes.length)
			{
				subVersion = bytes.readInt();
				if(subVersion == 2)
				{
					for(i = 0; i < numVertices; i++)
					{
						arrVertices[i].boneIds[0] = bytes.readShort();
						if(arrVertices[i].boneIds[0] != -1)
						{
							arrVertices[i].boneIds[1] = bytes.readShort();
							arrVertices[i].boneIds[2] = bytes.readShort();
							if(arrVertices[i].boneIds[1] != -1 && arrVertices[i].boneIds[2] != -1)
							{
								arrVertices[i].weights[0] = bytes.readUnsignedByte();
								arrVertices[i].weights[1] = bytes.readUnsignedByte();
								arrVertices[i].weights[2] = bytes.readUnsignedByte();
							}
						}
						arrVertices[i].extra = bytes.readInt();
					}
				}
				else if(subVersion == 1)
				{
					for(i = 0; i < numVertices; i++)
					{
						arrVertices[i].boneIds[0] = bytes.readShort();
						arrVertices[i].boneIds[1] = bytes.readShort();
						arrVertices[i].boneIds[2] = bytes.readShort();
						arrVertices[i].weights[0] = bytes.readUnsignedByte();
						arrVertices[i].weights[1] = bytes.readUnsignedByte();
						arrVertices[i].weights[2] = bytes.readUnsignedByte();
					}
				}
				else
				{
					// "Unknown subversion for vertex extra %d\n", subVersion);
				}
			}
			
			// joint extra
			if(bytes.position < bytes.length)
			{
				subVersion = bytes.readInt();
				if(subVersion == 1)
				{
					for(i = 0; i < numJoints; i++)
					{
						arrJoints[i].color = [bytes.readFloat(), bytes.readFloat(), bytes.readFloat()];
					}
				}
				else
				{
					//"Unknown subversion for joint extra %d\n", subVersion);
				}
			}
			
			// model extra
			if(bytes.position < bytes.length)
			{
				subVersion = bytes.readInt();
				if(subVersion == 1)
				{
					jointSize = bytes.readFloat();
					transparencyMode = bytes.readInt();
					alphaRef = bytes.readFloat();
				}
				else
				{
					//"Unknown subversion for model extra %d\n", subVersion);
				}
			}
		}

		public function clear() : void
		{
			
		}

		public function setupJoints() : void
		{
			for(var i : int = 0; i < arrJoints.length; i++)
			{
				arrJoints[i].parentIndex = getJointIndexByName(arrJoints[i].parentName);
			}
			
			var joint : Ms3d_joint;
			for(i = 0; i < arrJoints.length; i++)
			{
				joint = arrJoints[i];
				var matrix : Matrix3D = Matrix3D.angleMatrix(joint.rotation);
				matrix.n14 = joint.position[0];
				matrix.n24 = joint.position[1];
				matrix.n34 = joint.position[2];
				setArray2DFromMatrix3D(joint.matLocalSkeleton, matrix);
				
				if(joint.parentIndex == -1)
				{
					copyArray2D(joint.matLocalSkeleton, joint.matGlobalSkeleton);
				}
				else
				{
					var parentSkeleton : Array = arrJoints[joint.parentIndex].matGlobalSkeleton;
					var parentMatrix : Matrix3D = getMatrix3DFromArray2D(parentSkeleton);
					matrix = Matrix3D.multiply(parentMatrix, matrix);
					setArray2DFromMatrix3D(joint.matGlobalSkeleton, matrix);
				}
			}
			
			setupTangents();
		}

		private function getJointIndexByName(name : String) : int
		{
			for(var i : int = 0; i < arrJoints.length; i++)
			{
				if(arrJoints[i].name == name)
				{
					return i;
				}
			}
			return -1;
		}

		private function setupTangents() : void
		{
			var joint : Ms3d_joint;
			for(var i : int = 0; i < arrJoints.length; i++)
			{
				joint = arrJoints[i];
				var numPosKeys : int = joint.numKeyFramesTrans;
				joint.tangents = new Array(numPosKeys);
				
				for(var j : int = 0; j < numPosKeys; j++)
				{
					joint.tangents[j] = new Ms3d_tangent();
				}
				
				if(numPosKeys > 2)
				{
					for(var k : int = 0; k < numPosKeys; k++)
					{
						var k0 : int = k - 1;
						if(k0 < 0)
						{
							k0 = numPosKeys - 1;
						}
						var k1 : int = k;
						var k2 : int = k + 1;
						if(k2 >= numPosKeys)
						{
							k2 = 0;
						}
						
						var tangent : Array = new Array(3);
						tangent[0] = joint.keyFramesTrans[k2].position[0] - joint.keyFramesTrans[k0].position[0];
						tangent[1] = joint.keyFramesTrans[k2].position[1] - joint.keyFramesTrans[k0].position[1];
						tangent[2] = joint.keyFramesTrans[k2].position[2] - joint.keyFramesTrans[k0].position[2];
						
						var dt1 : Number = joint.keyFramesTrans[k1].time - joint.keyFramesTrans[k0].time;
						var dt2 : Number = joint.keyFramesTrans[k2].time - joint.keyFramesTrans[k1].time;
						var dt : Number = dt1 + dt2;
						joint.tangents[k1].tangentIn[0] = tangent[0] * dt1 / dt;
						joint.tangents[k1].tangentIn[1] = tangent[1] * dt1 / dt;
						joint.tangents[k1].tangentIn[2] = tangent[2] * dt1 / dt;
						joint.tangents[k1].tangentOut[0] = tangent[0] * dt2 / dt;
						joint.tangents[k1].tangentOut[1] = tangent[1] * dt2 / dt;
						joint.tangents[k1].tangentOut[2] = tangent[2] * dt2 / dt;
						
					}
				}
			}
		}

		public function setFrame(time : Number) : void
		{
			var i : int;
			var joint : Ms3d_joint;
			if(time < 0.0)
			{
				for(i = 0; i < arrJoints.length; i++)
				{
					joint = arrJoints[i];
					copyArray2D(joint.matLocalSkeleton, joint.matLocal);
					copyArray2D(joint.matGlobalSkeleton, joint.matGlobal);
//					joint.matLocal = joint.matLocalSkeleton;
//					joint.matGloblal = joint.matGlobalSkeleton;
				}
			}
			else
			{
				for(i = 0; i < arrJoints.length; i++)
				{
					evaluateJoint(i, time);
				}
			}
			currentTime = time;
		}

		private function evaluateJoint(index : int, time : Number) : void
		{
			var i : int;
			
			var joint : Ms3d_joint = arrJoints[index];
			
			var pos : Array = [0.0, 0.0, 0.0];
			var numPosKeys : int = joint.numKeyFramesTrans;
			if(numPosKeys > 0)
			{
				var i1 : int = -1;
				var i2 : int = -2;
				
				for(i = 0; i < numPosKeys - 1; i++)
				{
					if(time >= joint.keyFramesTrans[i].time && time < joint.keyFramesTrans[i + 1].time)
					{
						i1 = i;
						i2 = i + 1;
						break;
					}
				}
				
				if(i1 == -1 || i2 == -1)
				{
					if(time < joint.keyFramesTrans[0].time)
					{
						pos[0] = joint.keyFramesTrans[0].position[0];
						pos[1] = joint.keyFramesTrans[0].position[1];
						pos[2] = joint.keyFramesTrans[0].position[2];
					}
					else if(time >= joint.keyFramesTrans[numPosKeys - 1].time)
					{
						pos[0] = joint.keyFramesTrans[numPosKeys - 1].position[0];
						pos[1] = joint.keyFramesTrans[numPosKeys - 1].position[1];
						pos[2] = joint.keyFramesTrans[numPosKeys - 1].position[2];
					}
				}
				else
				{
					var p0 : Ms3d_keyframe_pos = joint.keyFramesTrans[i1];
					var p1 : Ms3d_keyframe_pos = joint.keyFramesTrans[i2];
					var m0 : Ms3d_tangent = joint.tangents[i1];
					var m1 : Ms3d_tangent = joint.tangents[i2];
					
					var t : Number = (time - p0.time) / (p1.time - p0.time);
					var t2 : Number = t * t;
					var t3 : Number = t2 * t;
					
					var h1 : Number =  2.0 * t3 - 3.0 * t2 + 1.0;
					var h2 : Number = -2.0 * t3 + 3.0 * t2;
					var h3 : Number =        t3 + 3.0 * t2 + t;
					var h4 : Number =        t3 -       t2;
					
					pos[0] = h1 * p0.position[0] + h3 * m0.tangentOut[0] + h2 * p1.position[0] + h4* m1.tangentIn[0];
					pos[1] = h1 * p0.position[1] + h3 * m0.tangentOut[1] + h2 * p1.position[1] + h4* m1.tangentIn[1];
					pos[2] = h1 * p0.position[2] + h3 * m0.tangentOut[2] + h2 * p1.position[2] + h4* m1.tangentIn[2];
				}
			}
			
			var quat : Array = [0.0, 0.0, 0.0, 0.0];
			var quaternion : Quaternion;
			var numRotKeys : int = joint.numKeyFramesRot;
			if(numRotKeys > 0)
			{
				var i3 : int = -1;
				var i4 : int = -1;
				
				for(i = 0; i < numRotKeys - 1; i++)
				{
					if(time >= joint.keyFramesRot[i].time && time < joint.keyFramesRot[i + 1].time)
					{
						i3 = i;
						i4 = i + 1;
						break;
					}
				}
				
				if(i3 == -1 || i4 == -1)
				{
					if(time < joint.keyFramesRot[0].time)
					{
						quaternion = Matrix3D.angleQuaternion(joint.keyFramesRot[0].rotation[0], joint.keyFramesRot[0].rotation[1], joint.keyFramesRot[0].rotation[2]);
					}
					else if(time >= joint.keyFramesRot[numRotKeys - 1].time)
					{
						quaternion = Matrix3D.angleQuaternion(joint.keyFramesRot[numRotKeys - 1].rotation[0], joint.keyFramesRot[numRotKeys - 1].rotation[1], joint.keyFramesRot[numRotKeys - 1].rotation[2]);
					}
				}
				else
				{
					t = (time - joint.keyFramesRot[i3].time) / (joint.keyFramesRot[i4].time - joint.keyFramesRot[i3].time);
					var q1 : Quaternion = Matrix3D.angleQuaternion(joint.keyFramesRot[i3].rotation[0], joint.keyFramesRot[i3].rotation[1], joint.keyFramesRot[i3].rotation[2]);
					var q2 : Quaternion = Matrix3D.angleQuaternion(joint.keyFramesRot[i4].rotation[0], joint.keyFramesRot[i4].rotation[1], joint.keyFramesRot[i4].rotation[2]);
					quaternion = Quaternion.slerp(q1, q2, t);
				}
			}
			
			var matrix : Matrix3D = quaternion.matrix;
			matrix.n14 = pos[0];
			matrix.n24 = pos[1];
			matrix.n34 = pos[2];
			
			var matLocal : Matrix3D = Matrix3D.multiply(getMatrix3DFromArray2D(joint.matLocalSkeleton), matrix);
			setArray2DFromMatrix3D(joint.matLocal, matLocal);
			
			if(joint.parentIndex == -1)
			{
				copyArray2D(joint.matLocal, joint.matGlobal);
			}
			else
			{
				var parentJoint : Ms3d_joint = arrJoints[joint.parentIndex];
				setArray2DFromMatrix3D(joint.matGlobal, Matrix3D.multiply(getMatrix3DFromArray2D(parentJoint.matGlobal), getMatrix3DFromArray2D(joint.matLocal)));
			}
		}

		public function transformVertex(vertex : Ms3d_vertex) : Array
		{
			var jointIndices : Array = new Array(4);
			var jointWeights : Array = new Array(4);
			fillJointIndicesAndWeights(vertex, jointIndices, jointWeights);
			
			if(jointIndices[0] < 0 || jointIndices[0] >= arrJoints.length || currentTime < 0.0)
			{
				return vertex.vertex;
			}
			else
			{
				var numWeights : int = 0;
				for(var i : int = 0; i < 4; i++)
				{
					if(jointWeights[i] > 0 && jointIndices[i] >= 0 && jointIndices[i] < arrJoints.length)
					{
						numWeights++;
					}
					else
					{
						break;
					}
				}
				
				var result : Array = [0.0, 0.0, 0.0];
				
				var weights : Array = [jointWeights[0] / 100.0, jointWeights[1] / 100.0, jointWeights[2] / 100.0, jointWeights[3] / 100.0];
				if(numWeights == 0)
				{
					numWeights = 1;
					weights[0] = 1.0;
				}
				// add weighted vertices
				for(i = 0; i < numWeights; i++)
				{
					var joint : Ms3d_joint = arrJoints[jointIndices[i]];
					var number3D : Number3D = new Number3D(vertex.vertex[0], vertex.vertex[1], vertex.vertex[2]);
					number3D = Matrix3D.iTransformVector(getMatrix3DFromArray2D(joint.matGlobalSkeleton), number3D);
					number3D = Matrix3D.transformVector(getMatrix3DFromArray2D(joint.matGlobal), number3D);
					
					result[0] += number3D.x * weights[i];
					result[1] += number3D.y * weights[i];
					result[2] += number3D.z * weights[i];
				}
				
				return result;
			}
		}

		private function fillJointIndicesAndWeights(vertex : Ms3d_vertex, indices : Array, weights : Array) : void
		{
			indices[0] = vertex.boneId;
			indices[1] = vertex.boneIds[0];
			indices[2] = vertex.boneIds[1];
			indices[3] = vertex.boneIds[2];
			weights[0] = 100;
			weights[1] = 0;
			weights[2] = 0;
			weights[3] = 0;
			if(vertex.weights[0] != 0 || vertex.weights[1] != 0 || vertex.weights[2] != 0)
			{
				weights[0] = vertex.weights[0];
				weights[1] = vertex.weights[1];
				weights[2] = vertex.weights[2];
				weights[3] = 100 - (vertex.weights[0] + vertex.weights[1] + vertex.weights[2]);
			}
		}

		public static function getMatrix3DFromArray2D(arr : Array) : Matrix3D
		{
			var mat : Matrix3D = new Matrix3D();
			mat.n11 = arr[0][0];
			mat.n12 = arr[0][1];
			mat.n13 = arr[0][2];
			mat.n14 = arr[0][3];
			mat.n21 = arr[1][0];
			mat.n22 = arr[1][1];
			mat.n23 = arr[1][2];
			mat.n24 = arr[1][3];
			mat.n31 = arr[2][0];
			mat.n32 = arr[2][1];
			mat.n33 = arr[2][2];
			mat.n34 = arr[2][3];
			return mat;
		}

		public static function setArray2DFromMatrix3D(arr : Array, mat : Matrix3D) : void
		{
			arr[0][0] = mat.n11;
			arr[0][1] = mat.n12;
			arr[0][2] = mat.n13;
			arr[0][3] = mat.n14;
			arr[1][0] = mat.n21;
			arr[1][1] = mat.n22;
			arr[1][2] = mat.n23;
			arr[1][3] = mat.n24;
			arr[2][0] = mat.n31;
			arr[2][1] = mat.n32;
			arr[2][2] = mat.n33;
			arr[2][3] = mat.n34;
		}

		public static function copyArray2D(from : Array, toArr : Array) : void
		{
			toArr[0][0] = from[0][0];
			toArr[0][1] = from[0][1];
			toArr[0][2] = from[0][2];
			toArr[0][3] = from[0][3];
			toArr[1][0] = from[1][0];
			toArr[1][1] = from[1][1];
			toArr[1][2] = from[1][2];
			toArr[1][3] = from[1][3];
			toArr[2][0] = from[2][0];
			toArr[2][1] = from[2][1];
			toArr[2][2] = from[2][2];
			toArr[2][3] = from[2][3];
		}

/* 	int GetNumVertices();
	void GetVertexAt(int nIndex, ms3d_vertex_t **ppVertex);
	int GetNumTriangles();
	void GetTriangleAt(int nIndex, ms3d_triangle_t **ppTriangle);
	int GetNumEdges();
	void GetEdgeAt(int nIndex, ms3d_edge_t **ppEdge);
	int GetNumGroups();
	void GetGroupAt(int nIndex, ms3d_group_t **ppGroup);
	int GetNumMaterials();
	void GetMaterialAt(int nIndex, ms3d_material_t **ppMaterial);
	int GetNumJoints();
	void GetJointAt(int nIndex, ms3d_joint_t **ppJoint);
	int FindJointByName(const char* lpszName);

	float GetAnimationFPS();
	float GetCurrentTime();
	int GetTotalFrames(); */
	}
}
